import json
from datetime import datetime, timedelta
from typing import Optional, Dict, Any
import httpx
from fastapi import HTTPException, UploadFile

from app.core.config import settings
from app.core.logging_config import get_logger
from app.utils.redis_client import redis_client

logger = get_logger(__name__)


class CapsuleService:
    """
    胶囊系统服务类，负责：
    1. ACL（Access Control List）鉴权管理
    2. Access Token获取和缓存
    3. 报告上传功能
    """

    # Redis缓存键
    ACL_SECRET_KEY = "capsule:acl_secret"
    ACCESS_TOKEN_KEY = "capsule:access_token"
    TOKEN_EXPIRE_KEY = "capsule:token_expire"

    def __init__(self):
        self.base_url = settings.CAPSULE_BASE_URL
        self.tenant_id = settings.CAPSULE_TENANT_ID
        self.acl_key = settings.CAPSULE_ACL_KEY
        self.timeout = settings.CAPSULE_REQUEST_TIMEOUT

    async def _get_cached_acl_secret(self) -> Optional[str]:
        """从Redis获取缓存的ACL secret"""
        try:
            acl_secret = await redis_client.get(self.ACL_SECRET_KEY)
            if acl_secret:
                logger.info("Using cached ACL secret")
                return acl_secret
        except Exception as e:
            logger.warning(f"Failed to get cached ACL secret: {e}")
        return None

    async def _cache_acl_secret(self, acl_secret: str, expire_time: str) -> None:
        """缓存ACL secret到Redis"""
        try:
            # 计算过期时间（秒）
            expire_dt = datetime.fromisoformat(expire_time.replace('Z', '+00:00'))
            now = datetime.now(expire_dt.tzinfo)
            expire_seconds = int((expire_dt - now).total_seconds())

            if expire_seconds > 0:
                await redis_client.set(self.ACL_SECRET_KEY, acl_secret, expire_seconds)
                logger.info(f"Cached ACL secret, expires in {expire_seconds} seconds")
        except Exception as e:
            logger.warning(f"Failed to cache ACL secret: {e}")

    async def get_acl(self) -> Dict[str, str]:
        """
        获取ACL（Access Control List）

        Returns:
            dict: 包含acl_key和acl_secret的字典
        """
        # 检查缓存
        cached_secret = await self._get_cached_acl_secret()
        if cached_secret:
            return {
                "acl_key": self.acl_key,
                "acl_secret": cached_secret
            }

        # 调用API获取ACL
        url = f"{self.base_url}/api/v1/acl/ak_sk"

        # 计算过期时间（30天后）
        expire_time = (datetime.now() + timedelta(days=30)).isoformat()
        create_time = datetime.now().isoformat()

        payload = {
            "tenant_uuid": self.tenant_id,
            "acl_key": self.acl_key,
            "expire_time": expire_time,
            "create_time": create_time
        }

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                response = await client.post(url, json=payload)
                response.raise_for_status()

                result = response.json()
                if result.get("code") != 200:
                    raise HTTPException(
                        status_code=500,
                        detail=f"Failed to get ACL: {result.get('msg', 'Unknown error')}"
                    )

                data = result.get("data", {})
                acl_secret = data.get("acl_secret")

                if not acl_secret:
                    raise HTTPException(status_code=500, detail="ACL secret not found in response")

                # 缓存ACL secret
                await self._cache_acl_secret(acl_secret, expire_time)

                logger.info("Successfully obtained ACL from API")
                return {
                    "acl_key": self.acl_key,
                    "acl_secret": acl_secret
                }

        except httpx.HTTPError as e:
            logger.error(f"HTTP error when getting ACL: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to get ACL: {str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error when getting ACL: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to get ACL: {str(e)}")

    async def _get_cached_access_token(self) -> Optional[str]:
        """从Redis获取缓存的access token"""
        try:
            token = await redis_client.get(self.ACCESS_TOKEN_KEY)
            if token:
                # 检查过期时间
                expire_time_str = await redis_client.get(self.TOKEN_EXPIRE_KEY)
                if expire_time_str:
                    expire_time = datetime.fromisoformat(expire_time_str.replace('Z', '+00:00'))
                    if datetime.now(expire_time.tzinfo) < expire_time:
                        logger.info("Using cached access token")
                        return token
        except Exception as e:
            logger.warning(f"Failed to get cached access token: {e}")
        return None

    async def _cache_access_token(self, token: str, expire_time: str) -> None:
        """缓存access token到Redis"""
        try:
            # 计算过期时间（秒）
            expire_dt = datetime.fromisoformat(expire_time.replace('Z', '+00:00'))
            now = datetime.now(expire_dt.tzinfo)
            expire_seconds = int((expire_dt - now).total_seconds())

            if expire_seconds > 0:
                await redis_client.set(self.ACCESS_TOKEN_KEY, token, expire_seconds)
                await redis_client.set(self.TOKEN_EXPIRE_KEY, expire_time, expire_seconds)
                logger.info(f"Cached access token, expires in {expire_seconds} seconds")
        except Exception as e:
            logger.warning(f"Failed to cache access token: {e}")

    async def get_access_token(self, force_refresh: bool = False) -> str:
        """
        获取Access Token

        Args:
            force_refresh: 是否强制刷新token

        Returns:
            str: access token
        """
        # 如果不强制刷新，检查缓存
        if not force_refresh:
            cached_token = await self._get_cached_access_token()
            if cached_token:
                return cached_token

        # 获取ACL
        acl = await self.get_acl()
        acl_secret = acl["acl_secret"]

        # 调用API获取access token
        url = f"{self.base_url}/api/v1/acl/access_token"

        headers = {
            "tenant-uuid": self.tenant_id,
            "Content-Type": "application/x-www-form-urlencoded"
        }

        data = {
            "acl_key": self.acl_key,
            "acl_secret": acl_secret
        }

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                response = await client.post(url, headers=headers, data=data)
                response.raise_for_status()

                result = response.json()
                if result.get("code") != 200:
                    raise HTTPException(
                        status_code=500,
                        detail=f"Failed to get access token: {result.get('message', 'Unknown error')}"
                    )

                data = result.get("data", {})
                access_token = data.get("access_token")
                expires_in = data.get("expires_in")

                if not access_token:
                    raise HTTPException(status_code=500, detail="Access token not found in response")

                # 缓存access token
                if expires_in:
                    await self._cache_access_token(access_token, expires_in)

                logger.info("Successfully obtained access token from API")
                return access_token

        except httpx.HTTPError as e:
            logger.error(f"HTTP error when getting access token: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to get access token: {str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error when getting access token: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to get access token: {str(e)}")

    async def upload_report(
        self,
        file: UploadFile,
        props: Dict[str, Any],
        signature: str = "1111111111"
    ) -> Dict[str, Any]:
        """
        上传报告到胶囊系统

        Args:
            file: 上传的文件对象
            props: 报告属性，包含type, owner, collector, collector_time等
            signature: 签名（可选）

        Returns:
            dict: 上传结果
        """
        # 获取access token
        access_token = await self.get_access_token()

        url = f"{self.base_url}/api/v1/capsules/collect"

        headers = {
            "signature": signature,
            "tenant-uuid": self.tenant_id,
            "token": access_token
        }

        # 准备文件数据
        file_content = await file.read()
        await file.seek(0)  # 重置文件指针

        files = {
            "file": (file.filename, file_content, file.content_type)
        }

        # 将props转换为JSON字符串
        data = {
            "props": json.dumps(props)
        }

        try:
            async with httpx.AsyncClient(timeout=self.timeout) as client:
                response = await client.post(url, headers=headers, files=files, data=data)
                response.raise_for_status()

                result = response.json()
                logger.info(f"Successfully uploaded report: {file.filename}")
                return result

        except httpx.HTTPStatusError as e:
            # 如果是401错误（鉴权失败），尝试刷新token重试一次
            if e.response.status_code == 401:
                logger.warning("Authentication failed, refreshing token and retrying...")
                try:
                    # 刷新token
                    access_token = await self.get_access_token(force_refresh=True)
                    headers["token"] = access_token

                    # 重新读取文件内容
                    await file.seek(0)
                    file_content = await file.read()
                    files = {
                        "file": (file.filename, file_content, file.content_type)
                    }

                    # 重试
                    async with httpx.AsyncClient(timeout=self.timeout) as client:
                        response = await client.post(url, headers=headers, files=files, data=data)
                        response.raise_for_status()

                        result = response.json()
                        logger.info(f"Successfully uploaded report after retry: {file.filename}")
                        return result

                except Exception as retry_error:
                    logger.error(f"Retry failed: {retry_error}")
                    raise HTTPException(
                        status_code=500,
                        detail=f"Failed to upload report after retry: {str(retry_error)}"
                    )
            else:
                logger.error(f"HTTP error when uploading report: {e}")
                raise HTTPException(
                    status_code=e.response.status_code,
                    detail=f"Failed to upload report: {str(e)}"
                )

        except httpx.HTTPError as e:
            logger.error(f"HTTP error when uploading report: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to upload report: {str(e)}")
        except Exception as e:
            logger.error(f"Unexpected error when uploading report: {e}")
            raise HTTPException(status_code=500, detail=f"Failed to upload report: {str(e)}")

    async def initialize(self) -> None:
        """
        初始化服务，在应用启动时调用
        获取并缓存ACL和access token
        """
        try:
            logger.info("Initializing capsule service...")

            # 获取ACL（会自动缓存）
            await self.get_acl()

            # 获取access token（会自动缓存）
            await self.get_access_token()

            logger.info("Capsule service initialized successfully")
        except Exception as e:
            logger.error(f"Failed to initialize capsule service: {e}")
            # 不抛出异常，允许应用继续启动


# 全局实例
capsule_service = CapsuleService()
